using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

using Bitboard = System.UInt64;

namespace OliveChess
{
    class Board
    {
        // TODO: change this table as private
        private Piece[] pieces;
        private Bitboard[] piecesBB;
        private Bitboard emptyBB;
        private Bitboard occupiedBB;

        public Board()
        {
            pieces = new Piece[64];
            piecesBB = new Bitboard[14];
            Clear();
        }

        /*
        * Create a new board by copy
        */
        public Board(Board b)
        {
            pieces = new Piece[64];
            piecesBB = new Bitboard[14];

            for (int i = 0; i < 64; i++)
            {
                pieces[i]= b.pieces[i];
            }
            emptyBB = b.emptyBB;
            occupiedBB = b.occupiedBB;
            
            for (int i = 0; i < 14; i++)
                piecesBB[i] = b.piecesBB[i];
        }

        public void Clear()
        {
            for (int i = 0; i < 64; i++)
            {
                pieces[i] = Piece.NO_PIECE;
            }
            emptyBB = 0xffffffffffffffff;
            occupiedBB = 0;
        }

        public void Clear(int square)
        {
            pieces[square] = Piece.NO_PIECE;
            emptyBB |= ((Bitboard)1 << square);
            occupiedBB &= ~((Bitboard)1 << square);
        }

        

        /**
         * Set a piece to the given square
         * must be used only for init
         * @param piece
         * @param square
         */
        public void SetPiece(Piece piece, int square)
        {
            Debug.Assert((square >= 0) && (square <= 63));
            Debug.Assert(piece != Piece.NO_PIECE);
            pieces[square] = piece;
            piecesBB[(int)piece] |= (Bitboard)1 << square;
            occupiedBB |= (Bitboard)1 << square ;
            emptyBB &= ~((Bitboard)1 << square);
        }

        /**
         * Get the content of a given square
         * 
         * @param square
         * @return
         */
        public Piece GetPiece(int square)
        {
            Debug.Assert((square >= 0) && (square <= 63)); 
            return pieces[square];
        }


        /**
         * Get all the legal moves for the given position
         * @return
         */
        /*
        public List<Move> GetLegalMoves()
        {
            List<Move> legalMoves = Engine.GetLegalMoves(this);
            //DebugDisplayMoves(possibleMoves);
            return legalMoves;
        }
         */

        /*
        private void DebugDisplayPossibleMoves(ArrayList<Move> possibleMoves) {
            System.out.println("Possible moves:");
            for(Move m : possibleMoves){
                System.out.print(m.toString() + " ");
            }
            System.out.println();
        }
        */



        /**
         * Perform a basic move or basic capture (no change of the turn)
         * 
         * @param start
         * @param dest
         */
        /*
        public void Move(int startSquare, int destSquare)
        {
            pieces[destSquare] = pieces[startSquare];
            pieces[startSquare] = Piece.NO_PIECE;
        }*/

        public void Move(Move move)
        {
            if ((move.cPiece == Piece.W_KING)
                || (move.cPiece == Piece.B_KING))
            {
                DebugDisplay();
                Console.WriteLine("Forviden move !");
                move.ToString();
                Debug.Assert(false);
            }
            Debug.Assert(piecesBB[(int)Piece.W_KING] != 0);
            Debug.Assert(piecesBB[(int)Piece.B_KING] != 0);
            Debug.Assert(move.from != move.to);

            pieces[move.to] = pieces[move.from];
            pieces[move.from] = Piece.NO_PIECE;

            Bitboard fromBB = (Bitboard)1 << move.from;
            Bitboard toBB = (Bitboard)1 << move.to;
            Bitboard fromToBB = fromBB ^ toBB; // |+
            piecesBB[(int)move.piece] ^= fromToBB;   // update piece bitboard
            piecesBB[move.color] ^= fromToBB;   // update white or black color bitboard
            if (move.cPiece == Piece.NO_PIECE)
            {
                // quiet move
                occupiedBB ^= fromToBB;   // update occupied ...
                emptyBB ^= fromToBB;   // ... and empty bitboard
            }
            else
            {
                // capture
                piecesBB[(int)move.cPiece] ^= toBB;       // reset the captured piece
                piecesBB[move.cColor] ^= toBB;       // update color bitboard by captured piece
                occupiedBB ^= fromBB;     // update occupied, only from becomes empty
                emptyBB ^= fromBB;     // update empty bitboard
            }
            

            /*
            Console.WriteLine("Occupied");
            BitboardHelper.DebugDisplay(occupiedBB);
            Console.WriteLine("empty");
            BitboardHelper.DebugDisplay(emptyBB);
             * */

            Debug.Assert(piecesBB[(int)Piece.W_KING] != 0);
            Debug.Assert(piecesBB[(int)Piece.B_KING] != 0);
        }

        public void Unmove(Move move)
        {
            Debug.Assert(piecesBB[(int)Piece.W_KING] != 0);
            Debug.Assert(piecesBB[(int)Piece.B_KING] != 0);
            Debug.Assert(move.from != move.to);

            pieces[move.from] = move.piece;
            pieces[move.to] = move.cPiece;

            Bitboard fromBB = (Bitboard)1 << move.from;
            Bitboard toBB = (Bitboard)1 << move.to;
            Bitboard fromToBB = fromBB ^ toBB; // |+
            piecesBB[(int)move.piece] ^= fromToBB;   // update piece bitboard
            piecesBB[move.color] ^= fromToBB;   // update white or black color bitboard
            if (move.cPiece == Piece.NO_PIECE)
            {
                // quiet move
                occupiedBB ^= fromToBB;   // update occupied ...
                emptyBB ^= fromToBB;   // ... and empty bitboard
            }
            else
            {
                // capture
                piecesBB[(int)move.cPiece] ^= toBB;       // reset the captured piece
                piecesBB[move.cColor] ^= toBB;       // update color bitboard by captured piece
                occupiedBB ^= fromBB;     // update occupied, only from becomes empty
                emptyBB ^= fromBB;     // update empty bitboard
            }

            /*
            Console.WriteLine("Occupied");
            BitboardHelper.DebugDisplay(occupiedBB);
            Console.WriteLine("empty");
            BitboardHelper.DebugDisplay(emptyBB);
             * */

            Debug.Assert(piecesBB[(int)Piece.W_KING] != 0);
            Debug.Assert(piecesBB[(int)Piece.B_KING] != 0);
        }


        /**
         * Indicates if the square is empty
         * 
         * @param square
         * @return
         */
        public bool IsSquareEmpty(int square)
        {
            return (pieces[square] == Piece.NO_PIECE);
        }



        public bool IsEqual(Board b)
        {
            // TODO: shorter with bitboards
            for (int i = 0; i < 64; i++) 
                if (pieces[i] != b.pieces[i]) return false;
            return true;
        }

        /*
        public override int GetHashCode()
        {
            int code = 0;
            for (int i = 0; i < 64; i++) code += ((int)this.pieces[i])*(13 ^ i);
            return code;
            
            //return this.pieces.GetHashCode();
        }
        */

        public int Evaluate(ref int[] coeff)
        {
            
            // classic eval part
            int result = 0;
            Piece p = Piece.NO_PIECE; ;
            for (int i = 0; i < 64; i++)
            {
                p = this.pieces[i];
                if (p != Piece.NO_PIECE)
                    result += coeff[(int)p];
            }

            /*
            // NOT OPTIMAL - too many operators
            int p_score = coeff[(int)Piece.W_PAWN] * (BitboardHelper.BitCount(piecesBB[(int)Piece.W_PAWN]) - BitboardHelper.BitCount(piecesBB[(int)Piece.B_PAWN]));
            int n_score = coeff[(int)Piece.W_KNIGHT] * (BitboardHelper.BitCount(piecesBB[(int)Piece.W_KNIGHT]) - BitboardHelper.BitCount(piecesBB[(int)Piece.B_KNIGHT]));
            int b_score = coeff[(int)Piece.W_BISHOP] * (BitboardHelper.BitCount(piecesBB[(int)Piece.W_BISHOP]) - BitboardHelper.BitCount(piecesBB[(int)Piece.B_BISHOP]));
            int r_score = coeff[(int)Piece.W_ROOK] * (BitboardHelper.BitCount(piecesBB[(int)Piece.W_ROOK]) - BitboardHelper.BitCount(piecesBB[(int)Piece.B_ROOK]));
            int q_score = coeff[(int)Piece.W_QUEEN] * (BitboardHelper.BitCount(piecesBB[(int)Piece.W_QUEEN]) - BitboardHelper.BitCount(piecesBB[(int)Piece.B_QUEEN]));
            // Not necessary - evaluated positions always have the two kings
            //int k_score = coef[(int)Piece.W_KNIGHT] * (BitboardHelper.BitCount(piecesBB[(int)Piece.W_KNIGHT]) - BitboardHelper.BitCount(piecesBB[(int)Piece.B_KNIGHT]));
            // TODO: implement mobility part
            //int mobility = BitboardHelper.BitCount() - BitboardHelper.BitCount();

            result = p_score + n_score + b_score + r_score + q_score;
             */

            return result;
        }

        public void DebugDisplay()
        {
            for (int i = 7; i >= 0; i--)
            {
                Console.WriteLine(" +---+---+---+---+---+---+---+---+");
                for (int j = 0; j < 8; j++)
                    Console.Write(" | " + ToString(pieces[i * 8 + j]));
                Console.WriteLine(" |");
            }
            Console.WriteLine(" +---+---+---+---+---+---+---+---+");
        }

        private String ToString(Piece p)
        {
            switch (p)
            {
                case Piece.W_PAWN:
                    return "P";
                case Piece.B_PAWN:
                    return "p";
                case Piece.W_KNIGHT:
                    return "N";
                case Piece.B_KNIGHT:
                    return "n";
                case Piece.W_BISHOP:
                    return "B";
                case Piece.B_BISHOP:
                    return "b";
                case Piece.W_ROOK:
                    return "R";
                case Piece.B_ROOK:
                    return "r";
                case Piece.W_QUEEN:
                    return "Q";
                case Piece.B_QUEEN:
                    return "q";
                case Piece.W_KING:
                    return "K";
                case Piece.B_KING:
                    return "k";
                default:
                    return " ";
            }
        }

        internal bool IsCheck(int side)
        {
            Debug.Assert(piecesBB[(int)Piece.W_KING] != 0);
            Debug.Assert(piecesBB[(int)Piece.B_KING] != 0);

            int square = 0;
            if(side==0)
                // check given by blacks to white king
                BitboardHelper.BitScanReverse(out square, piecesBB[(int)Piece.W_KING]);
            else
                // check given by whites to black king
                BitboardHelper.BitScanReverse(out square, piecesBB[(int)Piece.B_KING]);

            return IsCheck(square, 1-side);
        }
        /*
        public bool attacksToKing(int square, int side)
        {
            BitboardHelper bbh = BitboardHelper.Instance;

            Bitboard opPawns = piecesBB[(int)Piece.W_PAWN + side];
            Bitboard opKnights = piecesBB[(int)Piece.W_KNIGHT + side];
            Bitboard opRQ = piecesBB[(int)Piece.W_QUEEN + side];
            Bitboard opBQ = opRQ;
            opRQ |= piecesBB[(int)Piece.W_ROOK + side];
            opBQ |= piecesBB[(int)Piece.W_BISHOP + side];
            return (bbh.arrPawnAttacks[side,square] & opPawns)
                 | (bbh.arrKnightAttacks[square] & opKnights)
                 | (bbh.bishopAttacks(occupiedBB, square) & opBQ)
                 | (bbh.rookAttacks(occupiedBB, square) & opRQ)) ;
        }*/

        internal bool IsCheck(int square, int bySide)
        {
            Debug.Assert((square >= 0) && (square < 64));
            // TODO: change this workaround directly in the enum 'Piece'

            BitboardHelper bbh = BitboardHelper.Instance;
            Bitboard pawns = piecesBB[(int)Piece.W_PAWN + bySide];
            Bitboard knights = piecesBB[(int)Piece.W_KNIGHT + bySide];
            Bitboard king = piecesBB[(int)Piece.W_KING + bySide];

            if ((bbh.arrPawnAttacks[1 - bySide,square] & pawns)!=0) return true;
            if ((bbh.arrKnightAttacks[square] & knights) != 0) return true;
            if ((bbh.arrKingAttacks[square] & king)!=0) return true;
            Bitboard bishopsQueens = piecesBB[(int)Piece.W_QUEEN + bySide]
                              | piecesBB[(int)Piece.W_BISHOP + bySide];
            if ((bbh.bishopAttacks(occupiedBB, square) & bishopsQueens) != 0)
            {
                /*
                this.DebugDisplay();
                BitboardHelper.DebugDisplay(occupiedBB,"occupiedBB");
                BitboardHelper.DebugDisplay(emptyBB, "emptyBB");
                BitboardHelper.DebugDisplay((bbh.bishopAttacks(occupiedBB, square)),"bishopAttacks");
                BitboardHelper.DebugDisplay((bbh.bishopAttacks(occupiedBB, square) & bishopsQueens), "bishopAttacks & bishopsQueens");
                */

                return true;
            }
            Bitboard rooksQueens = piecesBB[(int)Piece.W_QUEEN + bySide]
                              | piecesBB[(int)Piece.W_ROOK + bySide];
            if ((bbh.rookAttacks(occupiedBB, square) & rooksQueens) != 0)
            {
                /*
                this.DebugDisplay();
                BitboardHelper.DebugDisplay(occupiedBB,"occupied");
                BitboardHelper.DebugDisplay(emptyBB, "emptyBB");
                BitboardHelper.DebugDisplay((bbh.rookAttacks(occupiedBB, square) ),"rookAttacks");
                BitboardHelper.DebugDisplay((bbh.rookAttacks(occupiedBB, square) & rooksQueens), "rookAttacks & rooksQueens");
                */
          
                return true;
            }
            return false;
        }
        

    }
}